﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Windows;
using System.Windows.Data;

namespace DataFiller.DataStorage.Extension.DataProvider
{
    /// <summary>
    /// get value to the propertypath through binding the model to the "Model" dependcy property
    /// </summary>
    public class DataEvaluator : DependencyObject
    {
        public string PropertyPath
        {
            get
            {
                return _propertyPath == null ?
                    string.Empty :
                    _propertyPath.Path;
            }
        }
        public static readonly DependencyProperty EvaluatedValueProperty = DependencyProperty.Register
            (
                "EvaluatedValue",
                typeof(object),
                typeof(DataEvaluator)
            );

        //must been dynamic, so that we could take advantage of runtime recognition 
        private dynamic _evaluatedValue;
        public dynamic EvaluatedValue
        {
            get
            {
                if (string.IsNullOrEmpty(_propertyPathAfterCollection))
                {
                    return GetValue(EvaluatedValueProperty);
                }
                else
                {
                    _evaluatedValue = GetValue(EvaluatedValueProperty);
                    return SelectProperty(_propertyPathAfterCollection);
                }
            }
            set
            {
                SetValue(EvaluatedValueProperty, value);
            }
        }

        protected PropertyPath _propertyPath;
        protected string _propertyPathAfterCollection = string.Empty;

        public void SetEvaluator(string path, object source, string collectionIdentifier = "", params object[] pathParam)
        {
            _propertyPathAfterCollection = string.Empty;
            string tempPath = string.Empty;
            _evaluatedValue = null;

            //for example : Model.BookCollection$BookName
            //means get the collection to BookName property
            if (!string.IsNullOrEmpty(collectionIdentifier))
            {
                var array = path.Split
                    (
                        new string[] { collectionIdentifier },
                        StringSplitOptions.RemoveEmptyEntries
                    );

                tempPath = array[0];
                _propertyPathAfterCollection = array[1];
            }
            else
            {
                tempPath = path;
            }

            _propertyPath = new PropertyPath(tempPath, pathParam);

            if (source != null)
                BindSource(source);
        }

        /// <summary>
        /// when the dependency property is set to be a collection, 
        /// use this method to select a sub collection contains one property values from the collection
        /// </summary>
        private IEnumerable SelectProperty(string propertyName)
        {
            return SelectPropertyThroughPath
                (
                    _evaluatedValue,
                    propertyName.Split('.')
                );
        }
        private void BindSource(object source)
        {
            try
            {
                if (BindingOperations.IsDataBound(this, EvaluatedValueProperty))
                    BindingOperations.ClearBinding(this, EvaluatedValueProperty);
            }
            catch (System.Exception e)
            {
                //there's may a bug to clear binding
            }

            Binding binding = new Binding();
            binding.Source = source;
            binding.Path = _propertyPath;
            binding.Mode = BindingMode.OneTime;
            BindingOperations.SetBinding(this, EvaluatedValueProperty, binding);
        }
        /// <summary>
        /// at present, we only support none collection property 
        /// </summary>
        private static Delegate AccessorGenerator<TSource>(params string[] propertyPaths)
        {
            if (propertyPaths == null || propertyPaths.Length == 0)
                throw new ArgumentNullException("property path is null");

            string paramName = "_source";
            Type sourceType = typeof(TSource);
            //List<PropertyInfo> pInfos = new List<PropertyInfo>();

            var _parameter = System.Linq.Expressions.Expression.Parameter(sourceType, paramName);

            System.Linq.Expressions.Expression currentNode = _parameter;
            foreach (var pName in propertyPaths)
            {
                currentNode = System.Linq.Expressions.Expression.Property(currentNode, pName);
            }
            var _resultExpression = System.Linq.Expressions.Expression.Lambda(currentNode, _parameter);
            return _resultExpression.Compile();
        }
        /// <summary>
        /// automatically transform the dynamic object into concrete type by runtime, through the parameter passing
        /// </summary>
        private static IEnumerable SelectPropertyThroughPath<T>(IEnumerable<T> collection, params string[] propertyNames)
        {
            if (collection == null || propertyNames == null || propertyNames.Length == 0)
                return null;

            Delegate getter = AccessorGenerator<T>(propertyNames);

            return collection.Select
            (
                p => getter.DynamicInvoke(p)
            );
        }
    }
}
